package sc.datainterchange;


import java.io.IOException;


/**
 * Reads and decodes protocol message fields.
 * 
 * @author stephencarmody@gmail.com Stephen Carmody
 */
public final class CodedInputBuffer
{
	private byte[] buffer;
	private int bufferSize = 0;
	private int bufferPos = 0;
	private int lastTag = 0;

	/** See setSizeLimit() */
	private int sizeLimit = DEFAULT_SIZE_LIMIT;

	private static final int DEFAULT_SIZE_LIMIT = 64 << 20; // 64MB

	/**
	 * Create a new CodedInputBuffer.
	 */
	public CodedInputBuffer()
	{
	}
	
	/**
	 * Makes the specified byte array the underlying byte array of this buffer.
	 * This buffers position and lastTag are reset to 0 and bufferSize is set to
	 * the length of the new byte array.
	 * 
	 * @param buffer
	 */
	public void put(byte[] buffer)
	{
		this.buffer = buffer;
		this.bufferSize = buffer.length;
		this.bufferPos = 0;
		this.lastTag = 0;
	}

	/**
	 * Set the maximum message size. In order to prevent malicious messages from
	 * exhausting memory or causing integer overflows, {@code CodedInputStream}
	 * limits how large a message may be. The default limit is 64MB. You should
	 * set this limit as small as you can without harming your app's
	 * functionality. Note that size limits only apply when reading from an
	 * {@code InputStream}, not when constructed around a raw byte array (nor
	 * with {@link ByteString#newCodedInput}).
	 * 
	 * @return the old limit.
	 */
	public int setSizeLimit(int limit)
	{
		if (limit < 0)
		{
			throw new IllegalArgumentException(
					"Size limit cannot be negative: " + limit);
		}
		int oldLimit = sizeLimit;
		sizeLimit = limit;
		return oldLimit;
	}

	/**
	 * Read one byte from the input.
	 * 
	 * @throws IOException
	 *             The end of the stream or the current limit was reached.
	 */
	public byte readRawByte() throws IOException
	{
		if (bufferPos == bufferSize)
			throw new IOException("The current limit was reached");
		
		return buffer[bufferPos++];
	}

	/**
	 * Read a fixed size of bytes from the input.
	 * 
	 * @throws IOException
	 *             The end of the stream or the current limit was reached.
	 */
	public byte[] readRawBytes(int size) throws IOException
	{
		if (size < 0)
			throw new IOException("Negative size");

		if (size <= bufferSize - bufferPos)
		{
			// We have all the bytes we need already.
			byte[] bytes = new byte[size];
			System.arraycopy(buffer, bufferPos, bytes, 0, size);
			bufferPos += size;
			return bytes;
		}
		else
			throw new IOException("The current limit was reached");
	}

	/**
	 * Reads and discards {@code size} bytes.
	 * 
	 * @throws InvalidProtocolBufferException
	 *             The end of the stream or the current limit was reached.
	 */
	public void skipRawBytes(int size) throws IOException
	{
		if (size < 0)
			throw new IOException("Negative size");

		if (size < bufferSize - bufferPos)
			// We have all the bytes we need already.
			bufferPos += size;
		else
			throw new IOException("The current limit was reached");
	}

	/**
	 * Attempt to read a field tag, returning zero if we have reached EOF.
	 * Protocol message parsers use this to read tags, since a protocol message
	 * may legally end wherever a tag occurs, and zero is not a valid tag
	 * number.
	 */
	public int readTag() throws IOException
	{
		if (bufferPos == bufferSize)
			return 0;
		
		lastTag = readRawVarint32();
		
		if (lastTag == 0)
			// If we actually read zero, that's not a valid tag.
			throw new IOException("Invalid tag");
		
		return lastTag;
	}

	/**
	 * Verifies that the last call to readTag() returned the given tag value.
	 * This is used to verify that a nested group ended with the correct end
	 * tag.
	 * 
	 * @throws InvalidProtocolBufferException
	 *             {@code value} does not match the last tag.
	 */
	public void checkLastTagWas(int value)
			throws IOException
	{
		if (lastTag != value)
			throw new IOException("Invalid End Tag");
	}

	/**
	 * Reads and discards a single field, given its tag value.
	 * 
	 * @return {@code false} if the tag is an endgroup tag, in which case
	 *         nothing is skipped. Otherwise, returns {@code true}.
	 */
	public boolean skipField(int tag) throws IOException
	{
		switch (WireFormat.getTagWireType(tag))
		{
		case WireFormat.WIRETYPE_VARINT:
			readInt32();
			return true;
		case WireFormat.WIRETYPE_FIXED64:
			readRawLittleEndian64();
			return true;
		case WireFormat.WIRETYPE_LENGTH_DELIMITED:
			skipRawBytes(readRawVarint32());
			return true;
		case WireFormat.WIRETYPE_START_GROUP:
			skipMessage();
			checkLastTagWas(WireFormat.makeTag(WireFormat
					.getTagFieldNumber(tag), WireFormat.WIRETYPE_END_GROUP));
			return true;
		case WireFormat.WIRETYPE_END_GROUP:
			return false;
		case WireFormat.WIRETYPE_FIXED32:
			readRawLittleEndian32();
			return true;
		default:
			throw new IOException("Invalid Wire Type");
		}
	}

	/**
	 * Reads and discards an entire message. This will read either until EOF or
	 * until an endgroup tag, whichever comes first.
	 */
	public void skipMessage() throws IOException
	{
		while (true)
		{
			int tag = readTag();
			if (tag == 0 || !skipField(tag))
				return;
		}
	}

	/** Read a {@code double} field value from the stream. */
	public double readDouble() throws IOException
	{
		return Double.longBitsToDouble(readRawLittleEndian64());
	}

	/** Read a {@code float} field value from the stream. */
	public float readFloat() throws IOException
	{
		return Float.intBitsToFloat(readRawLittleEndian32());
	}

	/** Read a {@code uint64} field value from the stream. */
	public long readUInt64() throws IOException
	{
		return readRawVarint64();
	}

	/** Read an {@code int64} field value from the stream. */
	public long readInt64() throws IOException
	{
		return readRawVarint64();
	}

	/** Read an {@code int32} field value from the stream. */
	public int readInt32() throws IOException
	{
		return readRawVarint32();
	}

	/** Read a {@code fixed64} field value from the stream. */
	public long readFixed64() throws IOException
	{
		return readRawLittleEndian64();
	}

	/** Read a {@code fixed32} field value from the stream. */
	public int readFixed32() throws IOException
	{
		return readRawLittleEndian32();
	}

	/** Read a {@code bool} field value from the stream. */
	public boolean readBool() throws IOException
	{
		return readRawVarint32() != 0;
	}

	/** Read a {@code string} field value from the stream. */
	public String readString() throws IOException
	{
		int size = readRawVarint32();
		if (size < bufferSize - bufferPos && size > 0)
		{
			// Fast path: We already have the bytes in a contiguous buffer, so
			// just copy directly from it.
			String result = new String(buffer, bufferPos, size, "UTF-8");
			bufferPos += size;
			return result;
		}
		else
		{
			// Slow path: Build a byte array first then copy it.
			return new String(readRawBytes(size), "UTF-8");
		}
	}

	/** Read a {@code uint32} field value from the stream. */
	public int readUInt32() throws IOException
	{
		return readRawVarint32();
	}

	/**
	 * Read an enum field value from the stream. Caller is responsible for
	 * converting the numeric value to an actual enum.
	 */
	public int readEnum() throws IOException
	{
		return readRawVarint32();
	}

	/** Read an {@code sfixed32} field value from the stream. */
	public int readSFixed32() throws IOException
	{
		return readRawLittleEndian32();
	}

	/** Read an {@code sfixed64} field value from the stream. */
	public long readSFixed64() throws IOException
	{
		return readRawLittleEndian64();
	}

	/** Read an {@code sint32} field value from the stream. */
	public int readSInt32() throws IOException
	{
		return decodeZigZag32(readRawVarint32());
	}

	/** Read an {@code sint64} field value from the stream. */
	public long readSInt64() throws IOException
	{
		return decodeZigZag64(readRawVarint64());
	}

	/**
	 * Read a raw Varint from the stream. If larger than 32 bits, discard the
	 * upper bits.
	 */
	public int readRawVarint32() throws IOException
	{
		byte tmp = readRawByte();
		if (tmp >= 0)
		{
			return tmp;
		}
		int result = tmp & 0x7f;
		if ((tmp = readRawByte()) >= 0)
		{
			result |= tmp << 7;
		}
		else
		{
			result |= (tmp & 0x7f) << 7;
			if ((tmp = readRawByte()) >= 0)
			{
				result |= tmp << 14;
			}
			else
			{
				result |= (tmp & 0x7f) << 14;
				if ((tmp = readRawByte()) >= 0)
				{
					result |= tmp << 21;
				}
				else
				{
					result |= (tmp & 0x7f) << 21;
					result |= (tmp = readRawByte()) << 28;
					if (tmp < 0)
					{
						// Discard upper 32 bits.
						for (int i = 0; i < 5; i++)
						{
							if (readRawByte() >= 0)
								return result;
						}
						throw new IOException("Malformed Varint");
					}
				}
			}
		}
		return result;
	}

	/** Read a raw Varint from the stream. */
	public long readRawVarint64() throws IOException
	{
		int shift = 0;
		long result = 0;
		while (shift < 64)
		{
			byte b = readRawByte();
			result |= (long) (b & 0x7F) << shift;
			if ((b & 0x80) == 0)
				return result;
			shift += 7;
		}
		throw new IOException("Malformed Varint");
	}

	/** Read a 32-bit little-endian integer from the stream. */
	public int readRawLittleEndian32() throws IOException
	{
		byte b1 = readRawByte();
		byte b2 = readRawByte();
		byte b3 = readRawByte();
		byte b4 = readRawByte();
		return (((int) b1 & 0xff)) | (((int) b2 & 0xff) << 8)
				| (((int) b3 & 0xff) << 16) | (((int) b4 & 0xff) << 24);
	}

	/** Read a 64-bit little-endian integer from the stream. */
	public long readRawLittleEndian64() throws IOException
	{
		byte b1 = readRawByte();
		byte b2 = readRawByte();
		byte b3 = readRawByte();
		byte b4 = readRawByte();
		byte b5 = readRawByte();
		byte b6 = readRawByte();
		byte b7 = readRawByte();
		byte b8 = readRawByte();
		return (((long) b1 & 0xff)) | (((long) b2 & 0xff) << 8)
				| (((long) b3 & 0xff) << 16) | (((long) b4 & 0xff) << 24)
				| (((long) b5 & 0xff) << 32) | (((long) b6 & 0xff) << 40)
				| (((long) b7 & 0xff) << 48) | (((long) b8 & 0xff) << 56);
	}

	/**
	 * Decode a ZigZag-encoded 32-bit value. ZigZag encodes signed integers into
	 * values that can be efficiently encoded with varint. (Otherwise, negative
	 * values must be sign-extended to 64 bits to be varint encoded, thus always
	 * taking 10 bytes on the wire.)
	 * 
	 * @param n
	 *            An unsigned 32-bit integer, stored in a signed int because
	 *            Java has no explicit unsigned support.
	 * @return A signed 32-bit integer.
	 */
	public static int decodeZigZag32(int n)
	{
		return (n >>> 1) ^ -(n & 1);
	}

	/**
	 * Decode a ZigZag-encoded 64-bit value. ZigZag encodes signed integers into
	 * values that can be efficiently encoded with varint. (Otherwise, negative
	 * values must be sign-extended to 64 bits to be varint encoded, thus always
	 * taking 10 bytes on the wire.)
	 * 
	 * @param n
	 *            An unsigned 64-bit integer, stored in a signed int because
	 *            Java has no explicit unsigned support.
	 * @return A signed 64-bit integer.
	 */
	public static long decodeZigZag64(long n)
	{
		return (n >>> 1) ^ -(n & 1);
	}

}
